#include "objfile.h"
#include <fstream>
#include <iostream>
#include <cerrno>
#include <cstring>
#include <sstream>
#include <vector>

namespace raster {

using Vec3f = mathengine::Vec3f;
using Vec2f = mathengine::Vec2f;

ObjFile::ObjFile(const char *filename)
{
    if (!filename || strlen(filename) == 0) {
        std::cerr << "Failed to open file: " << filename << ", reason: filename is null." << std::endl;
        return;
    }

    std::ifstream in(filename);
    if (!in.is_open())
    {
        char buf[1024];
#ifdef _WIN32
        strerror_s(buf, sizeof(buf), errno);
#else // POSIX.1-2001
        strerror_r(errno, buf, sizeof(buf));
#endif
        std::cerr << "Failed to open file: " << filename
                    << ". reason(errno=" << errno << "): " << buf << std::endl;
        return;
    }

    std::vector<int> vertIndices, normIndices, uvIndices;
    std::vector<Vec3f> tmp_vertices;
    std::vector<Vec3f> tmp_normals;
    std::vector<Vec2f> tmp_uvs;
    std::string line;
    int numVertices = 0, numNormals = 0, numUvs = 0, numFaces = 0;

    while (std::getline(in, line)) {
        if (line.empty()) {
            continue;
        }
        
        std::stringstream ss(line);
        std::string prefix;
        ss >> prefix;

        if (prefix.compare(0, 1, "#") == 0) { // comment
            continue;
        }
        else if (prefix == "v") { // vertex
            float x, y, z;
            ss >> x >> y >> z;
            tmp_vertices.push_back(Vec3f({x, y, z}));
            numVertices++;
        }
        else if (prefix == "vn") { // normal
            float x, y, z;
            ss >> x >> y >> z;
            tmp_normals.push_back(Vec3f({x, y, z}));
            numNormals++;
        }
        else if (prefix == "vt") { // texture coordinate
            float u, v;
            ss >> u >> v;
            tmp_uvs.push_back(Vec2f({u, v}));
            numUvs++;
        }
        else if (prefix == "f") { // "f v/t/n v/t/n v/t/n"
            char discard;
            int v[3], n[3], t[3];
            ss >> v[0] >> discard >> t[0] >> discard >> n[0] 
               >> v[1] >> discard >> t[1] >> discard >> n[1] 
               >> v[2] >> discard >> t[2] >> discard >> n[2];

            for (int i = 0; i < 3; i++) {
                vertIndices.push_back(v[i]);
                normIndices.push_back(n[i]);
                uvIndices.push_back(t[i]);
            }
            numFaces++;
        }
    }
    std::cout << "numVertices: " << numVertices << ", numNormals: " << numNormals << ", numUvs: " << numUvs << ", numFaces: " << numFaces << std::endl;

    // output vertices, uvs, normals.
    for (int i = 0; i < vertIndices.size(); i++) {
        if (vertIndices[i] > 0 && vertIndices[i] <= tmp_vertices.size()) {
            verts.push_back(tmp_vertices[vertIndices[i] - 1]);
        } else {
            std::cerr << "vertIndices[" << i << "] = " << vertIndices[i] << " is out of range." << std::endl;
        }

        if (normIndices[i] > 0 && normIndices[i] <= tmp_normals.size()) {
            norms.push_back(tmp_normals[normIndices[i] - 1]);
        } else {
            std::cerr << "normIndices[" << i << "] = " << normIndices[i] << " is out of range." << std::endl;
        }

        if (uvIndices[i] > 0 && uvIndices[i] <= tmp_uvs.size()) {
            uvs.push_back(tmp_uvs[uvIndices[i] - 1]);
        } else {
            std::cerr << "uvIndices[" << i << "] = " << uvIndices[i] << " is out of range." << std::endl;
        }
    }

    // check integrity
    if (verts.size() % 3 != 0) {
        std::cerr << "number of vertices (" << verts.size() << ") is not a multiple of 3 for file: " << filename << std::endl;
    }
    if (!norms.empty() && verts.size() != norms.size()) { // error
        std::cerr << "number of vertices (" << verts.size() << ") is not equal to number of normals (" << norms.size() 
                  << ") for file: " << filename << std::endl;
    }

    in.close();
}

} // namespace raster
